package edu.kpi.pzks2.ui;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;

import edu.kpi.pzks2.graph.Edge;
import edu.kpi.pzks2.graph.GraphBundle;
import edu.kpi.pzks2.graph.GraphBundle.EdgeDescriptor;
import edu.kpi.pzks2.graph.GraphBundle.NodeDescriptor;
import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.queue.NodeStatistDescriptor;
import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

@SuppressWarnings("serial")
public abstract class GraphEditor extends JPanel {
    final protected VisualizationViewer<Node, Edge> visual;
    final protected EditingModalGraphMouse<Node, Edge> graphMouse;
    
    protected int nodeId = 0;
    
    protected AbstractLayout<Node, Edge> layout;
    
    List<Node> selectedNodes;
    List<Edge> selectedEdges;
    
    public abstract Graph<Node, Edge> getNeededGraph();
    
    public GraphEditor() {
        final Graph <Node, Edge> graph = getNeededGraph();
        
        layout = new StaticLayout<Node, Edge>(graph);
        
        visual =  new VisualizationViewer<Node, Edge>(layout, new Dimension(500, 500));
        visual.setBackground(Color.white);

        visual.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Node>());
        visual.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<Edge>());
        visual.setVertexToolTipTransformer(visual.getRenderContext().getVertexLabelTransformer());
        visual.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
        
        Container content = this;
        final GraphZoomScrollPane panel = new GraphZoomScrollPane(visual);
        content.add(panel);
        
        Factory<Node> vertexFactory = new Factory<Node>() {
            public Node create() {
                return new Node(nodeId++);
            }
        };
        
        Factory<Edge> edgeFactory = new Factory<Edge>() {
            public Edge create() {
                return new Edge(nodeId++);
            }
        };
        
        graphMouse = 
            new EditingModalGraphMouse<Node, Edge>(visual.getRenderContext(), vertexFactory, edgeFactory);
        graphMouse.remove(graphMouse.getPopupEditingPlugin());
        
        visual.setGraphMouse(graphMouse);
        
        graphMouse.setMode(ModalGraphMouse.Mode.EDITING);
        
//        final PickedState<Node> pickedVertexState = ;
//        final PickedState<Edge> pickedEdgeState = ;

        visual.addKeyListener(new KeyListener() {
            public void keyTyped(KeyEvent arg0) {
                // do nothing
            }
            
            public void keyReleased(KeyEvent arg0) {
                if (!visual.getPickedVertexState().getPicked().isEmpty() && arg0.getKeyCode() == KeyEvent.VK_DELETE) {
                    for (Node n : visual.getPickedVertexState().getPicked()) {
                        visual.getGraphLayout().getGraph().removeVertex(n);
                    }
                    visual.repaint();
                }                
                if (!visual.getPickedEdgeState().getPicked().isEmpty() && arg0.getKeyCode() == KeyEvent.VK_DELETE) {
                    for (Edge e : visual.getPickedEdgeState().getPicked()) {
                        visual.getGraphLayout().getGraph().removeEdge(e);
                    }
                    visual.repaint();
                }                
            }
            
            public void keyPressed(KeyEvent arg0) {
                // do nothing
            }
        });

        Transformer<Node, Shape> shapeVertexPaint = new Transformer<Node,Shape>() {
            public Shape transform(Node arg0) {
                float size = 25;
                return new Ellipse2D.Float(-(size/2f), -(size/2f), size, size);
            }
        };

        visual.getRenderContext().setVertexShapeTransformer(shapeVertexPaint);
        //visual.getRenderContext().setVertexFillPaintTransformer(colorVertexPaint);
    }
    
    public AbstractLayout<Node, Edge> getGraphLayout() {
        return layout;
    }
    
    public void setGraphBundle(GraphBundle grBundle) {
        Graph <Node, Edge> graph = getNeededGraph();
        layout = new StaticLayout<Node, Edge>(graph);
        
        nodeId = 0;
        
        for (NodeDescriptor d : grBundle.getNodes()) {
            graph.addVertex(d.getNode());
            layout.setLocation(d.getNode(), d.getPos());
            if (nodeId < d.getNode().getId()) {
                nodeId = d.getNode().getId();
            }
        }
        nodeId++;
        
        for (EdgeDescriptor d : grBundle.getEdges()) {
            graph.addEdge(d.getEdge(), d.getPair().getFirst(), d.getPair().getSecond());
        }
        visual.setGraphLayout(layout);
        //visual.setGraphLayout(new FRLayout2<Node, Edge>(graph));
    }
    
    public void newGraph() {
        layout = new StaticLayout<Node, Edge>(getNeededGraph());
        nodeId = 0;
        visual.setGraphLayout(layout);
    }
    
    public void setPickingMode() {
        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
    }
    
    public void setEditingMode() {
        graphMouse.setMode(ModalGraphMouse.Mode.EDITING);
    }
    
    public abstract boolean isGraphValid();
    
    public List<NodeStatistDescriptor> getNodeDescList() {
        final Graph<Node, Edge> graph = visual.getGraphLayout().getGraph();
        
        // Process max critical path (length)
        int maxP = 0;
        for (Node node : graph.getVertices()) {
            int currentP = getCriticalPath(graph, node, false);
            if (currentP > maxP) {
                maxP = currentP;
            }
        }
        MainFrame.showLogMessage("Max critical path length = " + maxP);
        
        // Process max critical path (time)
        int maxT = 0;
        for (Node node : graph.getVertices()) {
            int currentT = getCriticalPath(graph, node, true);
            if (currentT > maxT) {
                maxT = currentT;
            }
        }
        MainFrame.showLogMessage("Max critical path weight = " + maxT);
        
        final List<NodeStatistDescriptor> res = new ArrayList<NodeStatistDescriptor>();
        
        for (Node node : graph.getVertices()) {
            final int criticalP = getCriticalPath(graph, node, false);
            final int criticalT = getCriticalPath(graph, node, true);
            final float normalizedSum = criticalP / (float) maxP + criticalT / (float) maxT;
            final int linkNum = graph.getNeighborCount(node);
            final NodeStatistDescriptor desc = 
                    new NodeStatistDescriptor(node, criticalT, criticalP, linkNum, normalizedSum);
            res.add(desc);
        }
        
        return res;
    }
    
    private int getCriticalPath(Graph<Node, Edge> graph, Node node, boolean isTime) {
        if (graph.getOutEdges(node).isEmpty()) {
            if (isTime) {
                return node.getWeight();
            } else {
                return 1;
            }
        }
        
        int maxPath = 0;
        for (Edge outEdge : graph.getOutEdges(node)) {
            final Node child = (Node) graph.getEndpoints(outEdge).getSecond();
            final int currentPath = getCriticalPath(graph, child, isTime);
            if (currentPath > maxPath) {
                maxPath = currentPath;
            }
        }
        
        if (isTime) {
            return maxPath + node.getWeight();
        } else {
            return maxPath + 1;
        }
    }
    
    
    
}
